WinZip 10.0 Keygenning
(RSA Defeating)

Date

par "Graftal"

 

jj / mois / aaaa

Page d'accueil de l'UIC

Publié par Quequero

Parce que la musique fait!

Greffe, bon comme d'habitude, merci beaucoup!

Parce que la musique fait!

....

Courriel: latfarg@gmail.com

Greffe @ irc.azzurra.org # crack-it #cryptorev #asm

....

Difficulté

( ) Débutants (X) Intermédiaire () Avancé () Master

 

introduction

Bonjour à tous. Dans ce tutoriel nous traiterons d'un programme historique tel que WinZip: arrivés à la dixième incarnation, les programmeurs ont décidé de (enfin) changer la routine

de protection: et c'est là que nous intervenons. Asseyez-vous et profitez de la balade. =)

Pièce jointe

Outils d'occasion

Les outils utilisés:

OllyDbg

Visual Studio 2003 + OpenSSL pour coder le keygen.

RSA Tool v2 facilement trouvé sur goooogle =)

URL ou FTP du programme

Eh bien, il ne faut pas beaucoup d'imagination pour le comprendre.

Rédaction


Très bien, commençons immédiatement à ouvrir le programme avec ollydbg et notons qu'il n'est ni packagé ni rien. Le nag nous accueille d'une manière chaleureuse pour dire le moins, et nous le renvoyons à Enter Registration Code et insérons Graftal comme nom d'utilisateur et 1234567890 comme numéro de série. Bp habituel comme GetWindowText et GetDlgItemText, puis nous faisons OK. Le deuxième de ces points de base nous amène à la zone de code suivante:

004136FF |> PUSH 101; / Compte = 101 (257.); Cas 1 de l'interrupteur0041368F

00413704 |. MOV EDI, WINZIP32.0054F350; | ASCII «Graftal»

00413709 |. PUSH EDI; | Tampon => WINZIP32.0054F350

0041370A |. PUSH 0C80; | ControlID = C80 (3200.)

0041370F   |. PUSH EBX; | hWnd

00413710 |. CALL DWORD PTR DS: [<& USER32.GetDlgItemTe>; \ GetDlgItemTextA

------------------- ~ snip ~ ------------------

00413724 |. PUSH 27; / Nombre = 27 (39.)

00413726 |. MOV ESI, WINZIP32.0054F454; | ASCII "1234567890"

0041372B |. PUSH ESI; | Tampon => WINZIP32.0054F454

0041372C   |. PUSH 0C81; | ControlID = C81 (3201.)

00413731 |. PUSH EBX; | hWnd

00413732 |. CALL DWORD PTR DS: [<& USER32.GetDlgItemTe>; \ GetDlgItemTextA

Ce qui nous intéresse vraiment ici, ce sont les variables Count qui peuvent aussi nous donner une indication POSSIBLE de la longueur de la série. Nous en avons certainement besoin pour créer le keygen, ne serait-ce qu'en ce qui concerne la longueur maximale du surnom. Il faut maintenant trouver le point de contrôle du feuilleton: faisons attention ici, car le programme est très dispersé et il m'a fallu un moment pour trouver le bon endroit pour aller étudier et inverser. La meilleure chose à faire dans ces cas, c'est de passer par-dessus tout et tout le monde, pour voir s'il y a des variables ou des vérifications qui peuvent attirer notre attention: à ce stade, nous entrerons plus en détail. D'où nous en sommes maintenant dans le code, commençons à passer à un code comme celui-ci:

0041383D |. CALL WINZIP32.004133BB

00413842 |. TEST AL, AL

00413844 |. POP ECX

00413845 |. JE WINZIP32.004138E3; il saute beaucoup plus bas en nous montrant le mendiant

0041384B |. PUSH EDI

0041384C   |. PUSH WINZIP32.0051A8F4;  ASCII "Nom1"

00413851 |. MOV EDI, WINZIP32.0051A808;  ASCII "WinZip"

00413856 |. PUSH EDI

00413857 |. CALL WINZIP32.00499F4B

0041385C   |. PUSH ESI

0041385D |. PUSH WINZIP32.0051A8F0;  ASCII "SN1"

00413862 |. PUSH EDI

00413863 |. CALL WINZIP32.00499F4B

00413868 |. CALL WINZIP32.00412B2A

0041386D |. PUSH WINZIP32.0051A810;  ASCII "winzip32.ini"

00413872 |. PUSH 0

00413874 |. PUSH 0

00413876 |. PUSH WINZIP32.0051A830;  ASCII "rrs"

On peut facilement deviner que l'essentiel du contrôle DEVRAIT avoir lieu dans l'appel que j'ai écrit ci-dessus: en fait, je dans le cas des données que nous avons saisies saute et donc le nom et le numéro de série ne sont pas enregistrés, si ce n'est en tant que périodiques corrects serait sauvé. Faisons F9 et recommençons OK, passons à l'étape et cette fois nous arrivons à l'appel 004133BB (celui ci-dessus en bref), entrons avec F7. À ce stade, nous sommes confrontés à beaucoup de code, pour la plupart absolument inutile. Comment le comprendre? Eh bien, il est important de parcourir tout le code la première fois, puis de trouver l'endroit où "creuser" =). En faisant cela, nous arrivons à ces instructions après un certain temps:

00413514 |. PUSH EDI; / Chaîne

00413515 |. CALL DWORD PTR DS: [<& KERNEL32.lstrlenA>]; \ lstrlenA

0041351B |. CMP EAX, 2

0041351E |. JL SHORT WINZIP32.0041352B

00413520 |. CMP EAX, 100

00413525 |. JG SHORT WINZIP32.0041352B

Autre chose à retenir lors du codage du keygen: la longueur du nom d'utilisateur doit être comprise entre 2 et 0x100. Si nous continuons à avancer, nous remarquerons que notre numéro de série apparaîtra aussi, puis c'est tout, l'appel se termine. Nous devons maintenant trouver le bon appel pour faire un pas et analyser. Un premier choix à faire pourrait être d'analyser cet appel:

0041348E |. MOV EDI, WINZIP32.0054F350;  ASCII «Graftal»

00413493 |. LEA EAX, DWORD PTR SS: [EBP-438]

00413499 |. MOV ECX, EDI

0041349B |. CALL WINZIP32.004124D2

Puisque nous trouvons notre nom d'utilisateur autour, il est possible que dans l'appel immédiatement en dessous, ou dans ceux qui le suivent, il y ait des instructions intéressantes à analyser. Le premier appel, cependant, même à un coup d'œil très superficiel, nous fait comprendre que rien d'important ne se passe là-dedans. Dans l'appel d'après, on trouve:

004E6980 / MOV EAX, DWORD PTR DS: [ECX]

004E6982 | MOV EDX, 7EFEFEFF

004E6987 | AJOUTER EDX, EAX

004E6989 | XOR EAX, FFFFFFFF

004E698C | XOR EAX, EDX

004E698E | AJOUTER ECX, 4

004E6991 | TEST EAX, 81010100

004E6996 | JE SHORT WINZIP32.004E6980

004E6998 | MOV EAX, DWORD PTR DS: [ECX-4]

004E699B | TEST AL, AL

004E699D | JE SHORT WINZIP32.004E69D1

004E699F | TEST AH, AH

004E69A1 | JE SHORT WINZIP32.004E69C7

004E69A3 | TEST EAX, 0FF0000

004E69A8 | JE SHORT WINZIP32.004E69BD

004E69AA | TEST EAX, FF000000

004E69AF | JE SHORT WINZIP32.004E69B3

004E69B1 \ JMP SHORT WINZIP32.004E6980

Après avoir inversé pendant un certain temps, nous commençons à comprendre que des valeurs telles que 0x7EFEFEFF et autres, en particulier dans ces contextes, sont des appels inutiles pour nos besoins. C'est un excellent critère pour éliminer certains des appels rencontrés en cours de route =). Cela dit (je trouve important d'expliquer certaines manières d '"éliminer" le code indésirable, surtout parce que c'est l'une des raisons qui poussent l'inverseur à abandonner le programme, ou qui en tout cas lui font perdre une myriade de temps ), nous utilisons une garde et très peu de cerveau: si la vérification de la longueur du nom d'utilisateur saisi est postérieure, il est fort probable que tout le code qui le précède ne soit pas nécessaire. En supposant cela, sautons tout jusqu'à ce que nous arrivions au strlen que je vous ai écrit auparavant et en particulier, lorsque nous sommes sur cet appel,

00413532 |. PUSH 0

00413534 |. PUSH EAX

00413535 |. CALL WINZIP32.004E68F0; cet appel

-------------------- ~ snip ~ --------------------------

00413554 |> MOV ESI, 12C

00413559 |. PUSH ESI

0041355A |. LEA EAX, DWORD PTR SS: [EBP-144]

00413560 |. PUSH EAX

00413561 |. PUSH EDI

00413562 |. CALL WINZIP32.00411EA8; alors analysons ça

EAX est «bcom», une chaîne que l'on retrouve relativement souvent dans ces instructions: ne soyons pas dupes, c'est inutile est string => même l'appel est inutile. Si vous voulez bien l'analyser, mais sachez que c'est une des façons dont le programme nous fait perdre un temps précieux: il faut être rapide pour trouver l'algo, sinon on s'ennuiera et on le lâchera. Et ce n'est pas bon =). Cependant, le deuxième appel contient déjà quelque chose qui peut attirer l'attention: en prenant un aperçu, on remarque également une constante, 0x1021. Si l'un d'entre vous avait déjà inversé winzip 8 ou 9, vous aurez sûrement remarqué qu'il s'agissait du masque utilisé par le programme pour ensuite générer la série. Une ampoule devrait déjà s’allumer ici. mais si on ne sait rien de la technique de protection des anciennes versions comment comprenez-vous? Pas de problème, passons à l'appel et voyons quel numéro de série est généré:

---------- ~ snip des boucles précédentes, nous nous intéressons uniquement à la série déjà générée et terminée -------------------

00411EF9 |. PUSH ECX; / Arg5

00411EFA |. MOVZX EAX, AX; |

00411EFD |. PUSH EAX; | Arg4

00411EFE |. PUSH WINZIP32.0051D9E8; | Arg3 = 0051D9E8 ASCII "% 04X% 04X"

00411F03 |. PUSH DWORD PTR SS: [EBP + 10]; | Arg2

00411F06 |. MOV BYTE PTR DS: [54F4C8], 1; |

00411F0D |. PUSH DWORD PTR SS: [EBP + C]; | Arg1

00411F10 |. CALL WINZIP32.0050755E; \ WINZIP32.0050755E

---------- ~ snip ~ ------------------------

Et ici dans la pile, immédiatement après l'appel dont (note pour le plus agrammatique: ce n'est pas une erreur, "cui" pas "ici") ci-dessus - qui est en fait un sprintf très simple - on retrouve notre belle série:
" 56AE08A3 "

Ce qui est en fait ma série pour les versions précédentes de winzip .. si vous ne le saviez pas, vous pouvez toujours essayer de la saisir en tant que série pour obtenir un gentil mendiant, ce qui vous dit peut-être même que votre série est ancienne et que vous devriez mettez-le à jour pour la version 10 du programme. Libéré de l'appel, on voit que quelques petites choses sont faites au serial généré il y a peu de temps, et d'autres appels se succèdent, inutiles, les uns après les autres, jusqu'à ce que nous arrivions enfin à notre série:

004135CB |. MOV EBX, WINZIP32.0054F454; ASCII "1234567890"

004135D0 |. PUSH EBX

004135D1 |. PUSH EDI

004135D2 |. CALL WINZIP32.004120FB; nous devrions jeter un oeil ici =)

Maintenant, exactement comme nous l'avons fait auparavant avec notre nom d'utilisateur, maintenant nous commençons à voir si les appels immédiatement en dessous de notre série sont intéressants ou non. Commençons par le premier:

00412118 |. MOV DWORD PTR SS: [EBP-150], EAX

0041211E |. MOV DWORD PTR SS: [EBP-14C], EAX

00412124 |. MOV DWORD PTR SS: [EBP-154], EAX

0041212A |. MOV DWORD PTR SS: [EBP-158], 0FFFF

00412134 |. CALL WINZIP32.004E8F04; oublions cet appel, trop compliqué =)

------------- ~ snip ~ ---------------------

0041214B |> / MOVZX EAX, BYTE PTR DS: [EDI]; prend le premier caractère du nom d'utilisateur

0041214E |. | PUSH EAX

0041214F   |. | CALL WINZIP32.004E768E; il fait une sorte de contrôle dont nous ne nous soucions pas

00412154 |. | TEST EAX, EAX; si eax == 1

00412156 |. | POP ECX

00412157 |. | JE SHORT WINZIP32.0041215E

00412159 |. | MOV AL, BYTE PTR DS: [EDI]; le char va bien et ..

0041215B |. | MOV BYTE PTR DS: [ESI], AL; ..est stocké dans une autre variable

0041215D |. | INC ESI

0041215E |> | INC EDI

0041215F   |. | CMP BYTE PTR DS: [EDI], 0

00412162 |. ^ \ JNZ SHORT WINZIP32.0041214B; refaire jusqu'à la fin du nom d'utilisateur

00412164 |> LEA EAX, DWORD PTR SS: [EBP-148]; nom d'utilisateur dans eax

0041216A |. PUSH EAX

0041216B |. MOV BYTE PTR DS: [ESI], 0

0041216E |. CALL WINZIP32.004E6950; renvoie strlen (nom d'utilisateur)

00412173 |. CMP EAX, 12C; si inférieur à 0x12C ..

00412178 |. POP ECX

00412179 |. JB SHORT WINZIP32.0041218C; ..alors ok

00412192 |. PUSH EAX; eax = nom d'utilisateur

00412193 |. CALL WINZIP32.00507CB3; CharLower (nom d'utilisateur)

--------------------- ~ blabla ~ ------------------

Voici donc un code commenté: je n'y mets que le début de l'appel car il est très long. Si vous avez avancé même un peu superficiellement dans le reste du code, vous remarquerez que d'autres instructions très similaires ou identiques à celles que nous avons analysées auparavant sont répétées, tandis que d'autres totalement nouvelles et que, à première vue, peuvent sembler importantes. Mais comment comprendre que l'appel est presque inutile? Disons que tout d'abord soit on passe un peu le cul, soit on voit que beaucoup, beaucoup de calculs sont effectués, mais au final ni notre série ni notre nom d'utilisateur, ni les longueurs qui peuvent y correspondre ni rien du tout n'apparaissent plus. C'est à ce stade que nous devons analyser un autre appel pour voir si au moins quelque chose s'y passe: sinon, l'appel manqué était probablement important =). Voici le prochain appel:

004135D7 |. LEA EAX, DWORD PTR SS: [EBP-438]

004135DD |. PUSH EBX

004135DE |. PUSH EAX

004135DF |. CALL WINZIP32.00411E19

Entrons à l'intérieur:

00411E19 / $ PUSH EBP

00411E1A |. MOV EBP, ESP

00411E1C |. PUSH DWORD PTR SS: [EBP + 8]

00411E1F |. CALL WINZIP32.00507CB3;  CharLower (nom d'utilisateur) est important!

00411E24 |. POP ECX

00411E25 |. PUSH DWORD PTR SS: [EBP + C]

00411E28 |. MOV ECX, DWORD PTR DS: [55CE00]

00411E2E |. PUSH DWORD PTR SS: [EBP + 8]

00411E31 |. PUSH WINZIP32.0051D9E0;  ASCII "SU_STD"

00411E36 |. CALL WINZIP32.004975F8;  nous devons absolument intervenir ici

00411E3B |. TEST AL, AL

00411E3D |. JE SHORT WINZIP32.00411E48

00411E3F |. MOV BYTE PTR DS: [54E5FB], 1

00411E46 |. JMP SHORT WINZIP32.00411E79

00411E48 |> PUSH DWORD PTR SS: [EBP + C]

00411E4B |. MOV ECX, DWORD PTR DS: [55CE00]

00411E51 |. PUSH DWORD PTR SS: [EBP + 8]

00411E54 |. PUSH WINZIP32.0051D9D8;  ASCII "SU_PRO"

00411E59 |. CALL WINZIP32.004975F8;  et puis ici

00411E5E |. TEST AL, AL

00411E60 |. JE SHORT WINZIP32.00411E72

00411E62 |. MOV BYTE PTR DS: [54E5FB], 1

00411E69 |. MOV BYTE PTR DS: [54E5FD], 1

00411E70 |. POP EBP

00411E71 |. RETN

00411E72 |> MOV BYTE PTR DS: [54E5FB], 0

00411E79 |> MOV BYTE PTR DS: [54E5FD], 0

00411E80 |. POP EBP

00411E81 \. RETN

Eh bien bien bien .. SU_STD = STANDARD et SU_PRO = PROFESSIONNEL .. au moins théoriquement =). Dans un moment, nous découvrirons si c'est vraiment le cas ou non.
Alors, commençons par entrer dans le premier appel signalé, celui immédiatement en dessous de SU_STD:

-------------------- ~ snip ~ -------------------------

00497641 |. FF70 08 PUSH DWORD PTR DS: [EAX + 8]

00497644 |. FF70 04 PUSH DWORD PTR DS: [EAX + 4]; "----- BEGIN RSA PUBLIC KEY ----- MBcCEADjFVf2ZJjxOFl + bqOr3O8CAwEAAQ == ----- END RSA PUBLIC KEY -----"

00497647 |. FF37 PUSH DWORD PTR DS: [EDI]

00497649 |. E8 A9C80600 APPEL WINZIP32.00503EF7

Hé hé, qu'est-ce que mes yeux voient! RSA? Si belle! =)
Ce qui est poussé est le contenu d'une clé PEM. Pour certains, une question se posera: qu'est-ce qu'une clé PEM? Disons que c'est un fichier .pem qui est utilisé pour stocker les clés rsa nécessaires pour crypter et décrypter les données. Dans ce cas, nous avons une jolie clé publique (pour des informations détaillées sur le rsa, je vous renvoie au tutoriel evilcry sur le sujet, vraiment sympa). Gardons donc les yeux ouverts pour toutes les fonctions inhérentes à RSA.
Si nous jetons un œil à l'intérieur de l'appel, nous verrons que la bibliothèque winzip appelée wzeay32 contient les fonctions concernant le rsa, et, juste pour donner un exemple, il y a deux appels aux fonctions appelées d2i_RSAPublicKey et PEM_ASN1_read_bio. En effectuant une toute petite recherche sur Internet, puisque j'avais remarqué la nette similitude entre des bibliothèques telles que libeay32 et wzeay32, vous pouvez facilement découvrir que wzeay32 est utilisé pour gérer les fonctions OpenSSL dans winzip =). Gardons à l'esprit que nous en aurons besoin plus tard pour créer le keygen (bien que ce ne soit pas essentiel).

Maintenant, commençons à avancer, en gardant à l'esprit que lorsque nous rencontrons du code dans lequel une chaîne est poussée qui a comme contenu ".. \ .. \ common \ WzReg.c" ou quelque chose de similaire, l'appel immédiatement précédent est inutile et donc ne le faisons pas perdre du temps à l'analyser. Cela dit, continuons à avancer, jetons un coup d'œil en appuyant sur Entrée lorsque nous sommes en ligne pour voir si cela fait du bien, et sinon, continuez jusqu'à ce que nous arrivions ici:

00497760 |. PUSH EAX

00497761 |. PUSH DWORD PTR SS: [EBP-44]

00497764 |. PUSH DWORD PTR DS: [EDI]

00497766 |. CALL WINZIP32.00503FC6; et voici la partie importante!

Voyons ce qui nous attend:

00503FEC |. CMP DWORD PTR SS: [EBP + C], 0; nom d'utilisateur = 0?

00503FF0 |. JE WINZIP32.005041EB; si oui, mendiant

00503FF6 |. TEST BYTE PTR DS: [ESI + 8], 2

00503FFA |. JE WINZIP32.00504197

00504000 |. MOV EDI, DWORD PTR SS: [EBP + 14]; serial = 0?

00504003 |. TEST EDI, EDI

00504005 |. JE WINZIP32.005041BB; si oui, mendiant

0050400B |. CMP DWORD PTR SS: [EBP + 18], 1E; longueur de série = 1E?

0050400F   |. JNZ WINZIP32.005041BB

Voici une chose fondamentale: notre série a une longueur de 0x0B (car le zéro final est également compté) alors qu'elle devrait contenir des caractères 0x1E. Faisons F9 et insérons cette série: 12345678901234567890123456789. Ok, revenons à l'endroit où nous étions auparavant, prenons quelques autres vérifications qui ont réussi, jusqu'à ce que nous atteignions cette boucle:

00504039 |> / CMP EDX, 100

0050403F   |. | JNB SHORT WINZIP32.00504056

00504041 |. | MOV AL, BYTE PTR DS: [ECX + EDI]

00504044 |. | CMP AL, 2D

00504046 |. | JE SHORT WINZIP32.00504050

00504048 |. | MOV BYTE PTR SS: [EBP + EDX-138], AL

0050404F   |. | INC EDX

00504050 |> | INC ECX

00504051 |. | CMP ECX, 1D

00504054 |. ^ \ JL SHORT WINZIP32.00504039

Je ne l'ai pas commenté car il y a peu à comprendre: il supprime simplement de la série tous les tirets qui devraient être là, mais que nous n'avons pas encore mis, et repositionne les caractères dans la chaîne pour qu'il n'y ait pas de "trous" (nous venons de supprimer les tirets, alors ..). Gardez toujours à l'esprit que 0x2D = '-'. Immédiatement après cette boucle, il y a un bel appel que nous allons analyser en profondeur car il est fondamental. Voyons comment ça commence:

00504FDD |> / MOV EAX, EDI; longueur en série sans tirets dans eax

00504FDF |. | SHL EAX, 3; eax * 8

00504FE2 |. | PUSH 5

00504FE4 |. | CDQ

00504FE5 |. | POP ECX

00504FE6 |. | IDIV ECX; eax / 5 avec reste dans edx

00504FE8 |. | EDX, TEST EDX; reste = 0? (donc eax multiple de 5?)

00504FEA |. | JNZ WINZIP32.005050F5; sinon, saute et mendiant

Eh bien, nous devons donc mettre beaucoup de tirets jusqu'à ce que notre 29 devienne un multiple de 5. Alors mettons 4 jolis tirets et faisons notre série comme ceci: 12345-78901-34567-90123-56789
Attention à REMPLACER les caractères qui étaient là avant avec les tirets, et de ne pas les ajouter, sinon le chèque que nous avons analysé avant donne mendiant off =). Le code que je vous montre maintenant est très long, mais n'ayez pas peur, j'explique les choses vraiment importantes là-dedans et immédiatement après je vais tout clarifier en vous l'expliquant sans les commentaires, car ce serait un gâchis monstrueux à faire les deux choses ensemble:

00505011 |> / MOV EAX, DWORD PTR SS: [EBP + 8]

00505014 |. | MOVZX EAX, BYTE PTR DS: [EAX]

00505017 |. | PUSH EAX

00505018 |. | CALL WINZIP32.004E9127; oublie ça, inutile

0050501D |. | CMP AL, 41; al = caractère de la série

0050501F   |. | POP ECX; basé sur le personnage qu'il trouve.

00505020 |. | JNZ SHORT WINZIP32.00505026; .. il le travaille pour lui donner de la valeur.

00505022 |. | XOR CL, CL; ..pour que cela se forme toujours.

00505024 |. | JMP SHORT WINZIP32.00505062; ..à partir de seulement 5 bits significatifs.

00505026 |> | CMP AL, 42

00505028 |. | JBE SHORT WINZIP32.00505032

0050502A |. | CMP AL, 49 ans

0050502C   |. | JNB SHORT WINZIP32.00505034

0050502E |. | SUB AL, 42

00505030 |. | JMP SHORT WINZIP32.00505060

00505032 |> | CMP AL, 49

00505034 |> | JBE SHORT WINZIP32.0050503E

00505036 |. | CMP AL, 4F

00505038 |. | JNB SHORT WINZIP32.00505040

0050503A |. | SUB AL, 43

0050503C   |. | JMP SHORT WINZIP32.00505060

0050503E |> | CMP AL, 4F

00505040 |> | JBE SHORT WINZIP32.0050504A

00505042 |. | CMP AL, 53 ans

00505044 |. | JNB SHORT WINZIP32.0050504C

00505046 |. | SUB AL, 44

00505048 |. | JMP SHORT WINZIP32.00505060

0050504A |> | CMP AL, 53

0050504C   |> | JBE SHORT WINZIP32.00505056

0050504E |. | CMP AL, 5A

00505050 |. | JA SHORT WINZIP32.00505056

00505052 |. | SUB AL, 45

00505054 |. | JMP SHORT WINZIP32.00505060

00505056 |> | CMP AL, 30;  Commutateur (cas 30..39)

00505058 |. | JB SHORT WINZIP32.005050D3

0050505A |. | CMP AL, 39 ans

0050505C   |. | JA SHORT WINZIP32.005050D3

0050505E |. | SUB AL, 1A;  Cas 30 («0»), 31 («1»), 32 («2»).

00505060 |> | MOV CL, AL

00505062 |> | MOV EAX, DWORD PTR SS: [EBP + C]; après quoi toutes les valeurs obtenues ..

00505065 |. | PUSH 8; ils sont "mixtes" comme expliqué plus loin

00505067 |. | CDQ

00505068 |. | POP EBX

00505069 |. | IDIV EBX

0050506B |. | CMP EDX, 7;  Commutateur (cas 0..7)

0050506E |. | JA SHORT WINZIP32.005050BF

00505070 |. | JMP DWORD PTR DS: [EDX * 4 + 505104]

00505077 |> | SHL CL, 3;  Cas 0 de l'interrupteur 0050506B

0050507A |. | JMP SHORT WINZIP32.005050B8

0050507C   |> | MOV AL, CL;  Cas 1 de l'interrupteur 0050506B

0050507E |. | SHR AL, 2

00505081 |. | OU BYTE PTR DS: [ESI], AL

00505083 |. | INC ESI

00505084 |. | SHL CL, 6

00505087 |. | JMP SHORT WINZIP32.005050B8

00505089 |> | SHL CL, 1;  Cas 2 de l'interrupteur 0050506B

0050508B |. | JMP SHORT WINZIP32.005050A9

0050508D |> | MOV AL, CL;  Cas 3 de l'interrupteur 0050506B

0050508F   |. | SHR AL, 4

00505092 |. | OU BYTE PTR DS: [ESI], AL

00505094 |. | INC ESI

00505095 |. | SHL CL, 4

00505098 |. | JMP SHORT WINZIP32.005050B8

0050509A |> | MOV AL, CL;  Cas 4 de l'interrupteur 0050506B

0050509C   |. | SHR AL, 1

0050509E |. | OU BYTE PTR DS: [ESI], AL

005050A0 |. | INC ESI

005050A1 |. | SHL CL, 7

005050A4 |. | JMP SHORT WINZIP32.005050B8

005050A6 |> | SHL CL, 2;  Cas 5 de l'interrupteur 0050506B

005050A9 |> | OU BYTE PTR DS: [ESI], CL

005050AB |. | JMP SHORT WINZIP32.005050BF

005050AD |> | MOV AL, CL;  Cas 6 de l'interrupteur 0050506B

005050AF |. | SHR AL, 3

005050B2 |. | OU BYTE PTR DS: [ESI], AL

005050B4 |. | INC ESI

005050B5 |. | SHL CL, 5

005050B8 |> | MOV BYTE PTR DS: [ESI], CL

005050BA |. | JMP SHORT WINZIP32.005050BF

005050BC |> | OU BYTE PTR DS: [ESI], CL;  Cas 7 de l'interrupteur 0050506B

005050BE |. | INC ESI

005050BF |> | MOV EBX, DWORD PTR SS: [EBP-4];  Cas par défaut du commutateur 0050506B

005050C2 |. | DEC EDI

005050C3 |. | INC DWORD PTR SS: [EBP + 8]

005050C6 |. | INC DWORD PTR SS: [EBP + C]

005050C9 |> | TEST EDI, EDI

005050CB |. ^ \ JG WINZIP32.00505011

Ok, essayons d'expliquer organiquement: que se passe-t-il au début de la boucle? En pratique, il prend les caractères du nom d'utilisateur et soustrait une valeur par défaut du code ascii en fonction de la valeur de caractère extraite de la série. Par exemple, si le caractère est un nombre (et est donc compris entre 0x30 et 0x39), il soustrait 0x1A et ainsi de suite. Cela sert à avoir une valeur où les bits significatifs sont au maximum 5. Après cela, dans la deuxième partie de la boucle si vous remarquez qu'il y a un commutateur avec 8 maisons .. 8 comme les bits qui composent un octet non? =) Bref, voici pratiquement ce que ça fait: imaginez courirla PREMIÈRE partie de la boucle pour tous les caractères de la série (celle qui fait SEULEMENT les soustractions), afin d'obtenir une série de valeurs. Imaginez maintenant de mettre dans un seul conteneur, disons une chaîne par exemple, tous les 0 et 1 qui composent les octets. Que verriez-vous? Eh bien, que les 3 premiers bits de chaque octet sont valides0. A ce stade alors, le programme supprime simplement ces 3 bits et déplace tout pour avoir une variable qui ne contient plus ces 3 bits. Mais donc ce que nous avions avant est "détruit" puisque maintenant les groupes de 8 bits qui composent 1 octet sont un ensemble de bits pris maintenant d'un caractère, maintenant d'un autre. Pour être clair, voici un exemple très simple:

Nous avons ces 3 octets

10011011 11110101 10111100

À travers la première partie de la boucle, nous les transformons en:

00011011 00010101 00011100

À travers la deuxième partie de la boucle, nous supprimons les 3 bits initiaux et transformons tout en ceci:

11011 10101 11100

Alors maintenant, le premier octet, juste pour donner un exemple, sera:

11011101

Et ainsi de suite pour tous les autres octets. Comme vous pouvez le voir, c'est très différent de ce que nous avions auparavant (100011011). Mais les caractères sont des multiples de 5 (contrairement à mon exemple) et donc au final en supprimant 3 bits de chaque octet, il est possible de former un nombre "droit" d'octets (il ne reste plus de bits qui n'atteignent pas l'octave., comme dans mon exemple). D'accord, j'espère que c'est clair, car c'était une partie importante =) (sinon, relisez le tout ou envoyez-moi un e-mail).

Nous arrivons ici à une autre partie importante: la génération d'un octet particulier. Jusqu'à présent, nous avons vu que le programme prend notre série, le modifie de manière appropriée pour obtenir une chaîne de 120 bits (15 caractères ASCII). Cette chaîne, notre "série numérique", a un octet final qui doit être très particulier. Le programme, dans le morceau de code ci-dessous, se donne pour tâche de vérifier sa validité effective; voyons selon quels paramètres:

005040A5 |> / MOV EAX, DWORD PTR DS: [EDI]; voyons ce que vaut edi sous peu ..

005040A7 |. | TEST EAX, EAX

005040A9 |. | MOV BL, 1; méfiez-vous de ce mov, il est à mettre dans le keygen

005040AB |. | JLE SHORT WINZIP32.005040E3

005040AD |. | MOV DWORD PTR SS: [EBP-148], EAX

005040B3 |> | / MOV EAX, DWORD PTR SS: [EBP-140]

005040B9 |. || PUSH 8

005040BB |. || CDQ

005040BC |. || POP ECX

005040BD |. || IDIV ECX

005040BF |. || MOV ESI, EDX

005040C1 |. || MOV EDX, 80; 0x80 =10 000 000 en bits

005040C6 |. || MOV ECX, ESI

005040C8 |. || SAR EDX, CL; shr edx, cl

005040CA |. || TEST BYTE PTR SS: [EBP + EAX-14], DL; ebp + eax-14 = notre série numérique

005040CE |. || JE SHORT WINZIP32.005040D2; sauter si le bit = 0

005040D0 |. || INC BL

005040D2 |> || INC DWORD PTR SS: [EBP-140]

005040D8 |. || DEC DWORD PTR SS: [EBP-148]

005040DE |. ^ | \ JNZ SHORT WINZIP32.005040B3

005040E0 |. | MOV ESI, DWORD PTR SS: [EBP + 8]

005040E3 |> | MOVZX EAX, BL; le nombre de bits dans eax

005040E6 |. | CDQ

005040E7 |. | PUSH 2

005040E9 |. | POP ECX

005040EA |. | IDIV ECX; divisez-le par 2

005040EC |. | MOV ECX, DWORD PTR SS: [EBP-144]; nombre qui est 7 et qui est décrémenté à chaque boucle

005040F2 |. | MOV AL, BYTE PTR SS: [EBP-5]; dernier octet de la série numérique dans al

005040F5 |. | SHL DL, CL

005040F7 |. | XOR AL, DL

005040F9 |. | TEST DL, AL; (dernier octet) et (valeur de dl)

005040FB |. | JNZ WINZIP32.0050418B; si ça saute, mendiant

00504101 |. | AJOUTER EDI, 4; passer au dword suivant

00504104 |. | DEC DWORD PTR SS: [EBP-144]; diminuer le nombre dont je parlais plus tôt

0050410A |. | CMP EDI, WINZIP32.0054B734; ASCII "WZRFWZUD"

00504110 |. ^ \ JL SHORT WINZIP32.005040A5; lorsque le nombre est inférieur à 3, la boucle s'arrête

Donc, comme vous pouvez le voir, il s'agit d'une double boucle. Je vais vous expliquer maintenant comment cela fonctionne. Fondamentalement, il prend le nombre 0x80 (qui est un nombre qui est transformé en binaire vaut 1000000) et fait un et ('test') vérifie si le premier bit du premier octet de notre "série numérique" est égal à 1. Si c'est, je ne saute pas et bl, qui prend en compte tous les bits qui sont 1 dans les 25 premiers bits de la série numérique, est incrémenté. Pourquoi ai-je mentionné les 25 premiers bits? Voyons ce que contient edi: les 4 premiers dwords sont égaux à 0x19 et le dernier à 0x14. Si vous ajoutez tout cela, vous voyez que c'est 0x78, qui est 120. Comme nos bits =). De cela, nous comprenons que winzip compte combien de bits = 1 il y a dans les 25 premiers bits de la série numérique, puis dans les 25 suivants et ainsi de suite jusqu'à ce qu'il n'en reste plus que 20.0100000 en binaire, 0x20 est 0010000 et ainsi de suite jusqu'à 1. Cela vous permet de vérifier les 8 bits. À la fin de tout cela, nous avons 5 valeurs (comme les 5 dwords contenus dans edi). La deuxième partie de la boucle explique ce que fait le programme avec ces 5 valeurs: tout d'abord il définit une variable = 7, puis prend la première valeur, la divise par deux, fait un shl entre le reste de la division et cette valeur initialement égale à 7. Le résultat est xé avec le dernier octet de la série numérique et un et est fait entre les deux valeurs: si le résultat est 0, alors tout va bien, si c'est 1, il saute au mendiant. A chaque boucle, le premier 7 est décrémenté.

Ok, nous avons presque terminé, attendez =). Si nous regardons le reste de l'appel dans lequel nous sommes, nous voyons deux appels intéressants: RSA_public_decrypt et SHA1.

NOM 

RSA_private_encrypt, RSA_public_decrypt - opérations de signature de bas niveau

SYNOPSIS 

 #include < openssl / rsa.h >

 int RSA_private_encrypt (int flen, unsigned char * from,

    unsigned char * to, RSA * rsa, int padding);

 int RSA_public_decrypt (int flen, unsigned char * from,

    unsigned char * to, RSA * rsa, int padding);

LA DESCRIPTION 

Ces fonctions gèrent les signatures RSA à un niveau bas.

RSA_private_encrypt () signe les Flen octets à partir (généralement un condensé de message avec un algorithme d' identification) à l' aide de la clé privée rsa et stocke la signature à . to doit pointer vers RSA_size (rsa) octets de mémoire.

padding désigne l'un des modes suivants:

RSA_PKCS1_PADDING

Rembourrage PKCS # 1 v1.5. Cette fonction ne gère pas l' algorithmeIdentifier spécifié dans PKCS # 1. Lors de la génération ou de la vérification des signatures PKCS # 1, rsa_sign (3) et rsa_verify (3) doivent être utilisés.

RSA_NO_PADDING

Signature RSA brute. Ce mode doit uniquement être utilisé pour mettre en œuvre des modes de remplissage du son cryptographiquement code d'application. La signature directe des données utilisateur avec RSA n'est pas sécurisée.

RSA_public_decrypt () récupère le condensé de la Flen octets de long signature à partir en utilisant la clé publique du signataire rsa . to doit pointer vers une section mémoire suffisamment grande pour contenir le résumé du message (qui est plus petit que RSA_size (rsa) - 11 ). padding est le mode de remplissage utilisé pour signer les données.

VALEURS DE RETOUR 

RSA_private_encrypt () renvoie la taille de la signature (c'est-à-dire RSA_size (rsa)). RSA_public_decrypt () renvoie la taille du résumé de message récupéré.

En cas d'erreur, -1 est renvoyé; les codes d'erreur peuvent être obtenus par err_get_error (3).

C'est la description des fonctions RSA_public_decrypt et des RSA_private_encrypt complémentaires (ce dernier que nous utiliserons dans le keygen). Donc notre fameux "série numérique" doit être une valeur retournée par RSA_private_encrypt, après quoi le programme va le déchiffrer et obtenir un nombre. Voyons tout cela, et voyons aussi bientôt ce qui est fait avec ce numéro:

00504112 |. | PUSH 3

00504114 |. | PUSH DWORD PTR DS: [ESI + 4]

00504117 |. | LEA EAX, DWORD PTR SS: [EBP-24]

0050411A |. | PUSH EAX; valeur de retour, nous faisons un Follow in dump sur EAX

0050411B |. | LEA EAX, DWORD PTR SS: [EBP-14]

0050411E |. | PUSH EAX

0050411F   |. | PUSH0F

00504121 |. | POP ESI

00504122 |. | PUSH ESI

00504123 |. | APPEL DWORD PTR DS: [55EF28]; wzeay32.RSA_public_decrypt

Si vous avez fait le suivi d'un vidage, gardez à l'esprit la valeur du premier dword, car nous le retrouverons bientôt.

En allant un peu plus loin dans la liste, il y a la fonction de hachage SHA1. Si j'étais vous, j'aurais déjà deviné ce qui va se passer, mais je ne gâcherai pas encore la surprise: P.

SYNOPSIS 

#include <openssl / sha.h>
 unsigned char * SHA1 (const unsigned char * d, unsigned long n,
                  unsigned char * md);
 
LA DESCRIPTION

SHA1()calcule le résumé de message SHA-1 des n octets en d et le place dans md (qui doit avoir de l'espace pour SHA_DIGEST_LENGTH == 20 octets de sortie). Si md est NULL, le condensé est placé dans un tableau statique.

Maintenant que nous savons quels paramètres prend la fonction de hachage, voyons le désasme:

00504140 |. | PUSH EAX; variable où contenir le hachage

00504141 |. | PUSH DWORD PTR SS: [EBP + 10]; longueur du hachage (7 dans le cas de Graftal)

00504144 |. | PUSH DWORD PTR SS: [EBP + C]; string to hashare (greffe en LOWERCASE je recommande)

00504147 |. | APPEL DWORD PTR DS: [55EF20]; CALL un hachage SHA1

0050414D |. | PUSH ESI

0050414E |. | LEA EAX, DWORD PTR SS: [EBP-14]

00504151 |. | PUSH 0

00504153 |. | PUSH EAX

00504154 |. | CALL WINZIP32.004E68F0          

00504159 |. | PUSH 0E                         

0050415B |. | LEA EAX, DWORD PTR SS: [EBP-38]

0050415E |. | PUSH EAX

0050415F   |. | LEA EAX, DWORD PTR SS: [EBP-13]

00504162 |. | PUSH EAX

00504163 |. | CALL WINZIP32.004E7E20          

00504168 |. | PUSH ESI

00504169 |. | LEA EAX, DWORD PTR SS: [EBP-24]

0050416C   |. | PUSH EAX

0050416D |. | LEA EAX, DWORD PTR SS: [EBP-14]

00504170 |. | PUSH EAX

00504171 |. | CALL WINZIP32.004E95C0; voici le vrai chèque

Tout cela n'était pas vraiment fondamental, nous trouvons la partie importante dans l'appel que j'ai marqué pour vous et que nous allons maintenant analyser, même si ce n'est pas très minutieusement, car cela ne sert à rien:

----- ~ snip ~ -----

004E961D |. REPE CMPS DWORD PTR ES: [EDI], DWORD PTR DS: [ESI]; esi = hash de notre nom d'utilisateur, edi = hash extrait de la série

004E961F |. JE SHORT WINZIP32.004E9648; s'ils sont les mêmes, sautez et le numéro de série est correct, ole!

----- ~ snip ~ -----

Hé hé hé! Qu'est ce que je vois! =)

Dans edi se trouve la valeur trouvée par la fonction RSA_public_decrypt! Mais c'est gentil hein? Voici le "secret": si la fonction RSA renvoie le hash de notre nom d'utilisateur, nous sommes enregistrés =).

Ok, on a beaucoup parlé du RSA mais .. tu ne penses pas qu'il manque encore quelque chose pour compléter le tableau? Ouais, qu'en est-il des clés? Où les mettons-nous? Lesquels sont-ils? =). Toutes les questions auxquelles je vais répondre sous peu. Tout d'abord, rappelez-vous que le programme avait deux modes d'enregistrement,le SU_STD e le SU_PRO? Voyons ici quelle clé SU_STD et SU_PRO utilise dans la fonction RSA_public_decrypt:

----- ~ snip ~ -----

00504112 |. 6A 03 | PUSH 3

00504114 |. FF76 04 | PUSH DWORD PTR DS: [ESI + 4]; Follow in dump sur cette variable

00504117 |. 8D45 DC | LEA EAX, DWORD PTR SS: [EBP-24]

0050411A |. 50 | PUSH EAX

0050411B |. 8D45 EC | LEA EAX, DWORD PTR SS: [EBP-14]

0050411E |. 50 | PUSH EAX

0050411F   |. 6A0F | PUSH 0F

00504121 |. 5E | POP ESI

00504122 |. 56 | PUSH ESI

00504123 |. FF15 28EF5500 | APPEL DWORD PTR DS: [55EF28]; RSA_public_decrypt

----- ~ snip ~ -----


Comme il s'agit d'une structure bignum, cela peut être un peu déroutant au début, mais en regardant un peu plus bas notre joli Follow in dump, nous pouvons voir une belle rangée de nombres longs 0x0F qui sont également pointés un peu plus haut dans la structure. En l'occurrence, la fonction prend 0x0F octets. Sera-ce une coïncidence? Naa: P. Evidemment, marquez ces octets au contraire et voici la première clé, notre N pour le SU_STD: E31557F66498F138597E6EA3ABDCEF.

Répétant la même chose pour le SU_PRO: 9D845872E117501775DD51D9044945.

Maintenant, utilisez RSA Tool v2 (si vous ne l'avez pas, recherchez-le sur Google et trouvez-le, il est relativement bien connu) et dites-lui de trouver p et q, sans vous soucier de l'exposant qui est l'habituel 0x10001. J'écris toutes les données ici:

SU_STD:

n = E31557F66498F138597E6EA3ABDCEF

p = EC5A57BA9E77CFB

q = F5F5BE226C8F59D

e = 0x10001

d = 16B67CCCD53502F0870D775052FB49

SU_PRO:

n = 9D845872E117501775DD51D9044945

p = C2C17D26DBC6E83

q = CF0D008F6995E97

e = 0x10001

d = 1E84F8063A2AA42B29DD5578421E79

Ils sont tous dérivés de l'outil RSA. Avec ces données, nous pouvons enfin coder le keygen =).

~ KeyGen ~

Concernant le keygen, il n'y a pas grand chose à dire: je résume ici les étapes du keygen et j'attache le src au keygen que j'ai créé. Je ne veux pas, j'avais commencé à écrire le keygen en asm, mais quand j'ai décidé d'implémenter OpenSSL j'ai préféré l'écrire en C: par conséquent, certaines pièces sont faites en asm dans le source, mais elles se comprennent assez bien; disons simplement que c'est anti-esthétique. Et puis si vous ne les comprenez pas, qu'allez-vous inverser? : P

Ok alors, notre keygen doit faire exactement ceci:

1)      Prenez le nom d'utilisateur et mettez-le en minuscules.

2)      Hash la chaîne obtenue.

3)      Initialisez les clés RSA.

4)      Faites un RSA_private_encrypt sur notre hachage.

5)      Créez le dernier octet en comptant les bits = 1 présents dans les autres.

6)      Créez la série en caractères en effectuant le processus inverse effectué par le programme et en mettant les tirets.

7)      Tout cela pour les deux types de séries bien sûr, standard et professionnel.

Ici, comme devoirs, codez un joli keygen propre et envoyez-moi le src comme d'habitude au mail latfarg (at) gmail (dot) com =).

Greffe

Notes finales

C'était le tutoriel, j'espère que vous l'avez apprécié : P. Il m'a fallu beaucoup de temps pour l'écrire en raison du manque de temps ( : P), mais à la fin je l'ai fait. Je voudrais remercier tous ceux que je connais, en particulier Eimiar, le mythique cri diabolique ; ), le clan UT2004 auquel j'appartiens et comme toujours le Que qui publie très gentiment mon matériel: P.

Si vous avez des commentaires, des conseils, des doutes, envoyez tout à latfarg (at) gmail (dot) com = )

À la prochaine.

Avertissement

Je vous rappelle que le logiciel doit être acheté et non volé, vous devez enregistrer votre produit après la période d'évaluation. Je ne suis pas responsable des dommages causés à votre ordinateur par une mauvaise utilisation de ce didacticiel. Ce document a été écrit pour inciter le consommateur à enregistrer légalement ses programmes, et non pour lui faire utiliser les nombreux fichiers de crack sur le net, en fait ce document aide à comprendre l'effort que chaque développeur a dû faire pour fournir son consommateurs respectifs, les meilleurs produits possibles.

Nous l'inversons à titre informatif uniquement et pour améliorer nos connaissances du langage d'assemblage. 

Texte d'origine